import 'dart:async';
import 'dart:io';

import 'package:background_downloader/background_downloader.dart';
import 'package:dio/dio.dart';
import 'package:ffmpeg_kit_flutter/ffmpeg_kit.dart';
import 'package:ffmpeg_kit_flutter/ffmpeg_session.dart';
import 'package:ffmpeg_kit_flutter/log.dart';
import 'package:ffmpeg_kit_flutter/return_code.dart';
import 'package:ffmpeg_kit_flutter/statistics.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter_download_plugin/utils/video/m3u8_downloader.dart';
import 'package:flutter_download_plugin/utils/video/m3u8_utils.dart';
import 'package:flutter_download_plugin/utils/video/video_utils.dart';

class M3u8DownloadSegments {
  final Dio _dio = Dio();
  int maxConcurrentDownloads = 16;
  M3u8DownloaderTask parentTask;
  final List<M3u8DownloadSegmentTask> _taskQueue = [];
  Timer? progressTimer;

  //分片的基础信息
  //已下载部分的bytes
  int totalBytes = 0;
  int totalSegments = 0;

  //所有分片加起来的总速度,单位字节
  int totalSpeed = 0;

  //合并后的文件路径
  String outputFilePath = "";

  //使用ffmpeg下载时
  FFmpegSession? session;

  ///是否使用ffmpeg托管下载
  bool useFfmpeg = true;

  int lastProgressTime = DateTime.now().millisecondsSinceEpoch; // 记录上一次下载进度的时间
  int lastDownloadedBytes = 0; // 记录上一次下载bytes的大小用来计算速度时用

  ///回调方法[totalSegments]总计的分片数量
  void Function(int totalBytes, int totalSegments) onStartDownload;

  ///progress 0-1
  void Function(double progress, int downloadedBytes, int totalBytes, int downloadedSegments, int totalSegments, int speed) onProgress;
  void Function(String msg) onFailed;
  void Function() onPause;

  ///progress  0-1
  void Function(String fileMp4Path, double progress, int downloadedBytes, int totalBytes, int downloadedSegments, int totalSegments) onSuccess;

  M3u8DownloadSegments({
    required this.parentTask,
    required this.onStartDownload,
    required this.onPause,
    required this.onProgress,
    required this.onFailed,
    required this.onSuccess,
  }); // 添加任务

  ///开始下载
  void startDownload() async {
    if (parentTask.segments == null) {
      if (kDebugMode) {
        print("${parentTask.directory}分片信息为空,跳过此下载任务");
      }
      return;
    }

    if (parentTask.segments!.isEmpty) {
      return;
    }

    //根据url情况判断是否使用ffmpeg下载
    useFfmpeg = isUseFfmpeg();

    //所有分片都加到任务队列里面去
    for (int i = 0; i < parentTask.segments!.length; i++) {
      try {
        _addDownloadTask(url: parentTask.segments![i].url!, index: i);
      } catch (e) {
        onFailed("下载失败");
        if (kDebugMode) {
          print("下载分片失败:$e");
        }
        return;
      }
    }
    //如果整个任务已经暂停的话则直接不处理了
    if (parentTask.status == TaskStatus.paused) {
      return;
    }

    calculateTotalBytes();

    if (isAllTaskComplete()) {
      //这里可以进行判断已下载的bytes和总共的bytes是否一致不一致的话可以认为下载失败了(这个版本暂时不做)
      stopTimer();
      onCallBackProgress();
      onCallBackSuccess();
      return;
    }

    if (!useFfmpeg) {
      ///实时返回进度
      Timer.periodic(const Duration(milliseconds: 1000), (timer) {
        if (!isHadRunningTask() || parentTask.status != TaskStatus.running) {
          stopTimer();
          return;
        }
        //每次计算进度时都算一次总下载大小，因为查询头文件大小信息的地方是异步的
        calculateTotalBytes();

        int currentTime = DateTime.now().millisecondsSinceEpoch;
        int timeDiff = currentTime - lastProgressTime; // 计算时间间隔（毫秒）
        if (timeDiff > 0) {
          int downloadedBytes = 0;
          for (var task in _taskQueue) {
            downloadedBytes += task.downloadedBytes;
          }

          int downloadedDiffBytes = downloadedBytes - lastDownloadedBytes; // 计算这段时间内下载的字节数
          totalSpeed = downloadedDiffBytes ~/ timeDiff * 1000; // 计算速度（字节/秒）

          lastProgressTime = DateTime.now().millisecondsSinceEpoch;
          lastDownloadedBytes = downloadedBytes;
        }

        progressTimer = timer;

        onCallBackProgress();
      });
    }

    // int totalBytes = 0;
    // for (var task in _taskQueue) {
    //   totalBytes += task.totalLength;
    // }
    onStartDownload(totalBytes, _taskQueue.length);

    if (useFfmpeg) {
      //ffmpeg下载时不适用自定义的下载方式,使用ffmpeg下载，使用dio下载部分代码在_addDownloadTask里
      startDownloadByFfmpeg();
    }
  }

  // ///开始下载分片任务队列
  // void startDownloadTask()  {
  //
  // }

  Future<void> _addDownloadTask({
    required String url,
    required int index,
  }) async {
    if (parentTask.status == TaskStatus.paused) {
      return;
    }

    ///生成下载基本信息
    M3u8DownloadSegmentTask task = M3u8DownloadSegmentTask(url: url);

    task.directory = parentTask.directory; //每个m3u8文件都放在一个文件夹内
    task.fileName = "video_$index${VideoUtils.getSuffix(url)}";
    task.filePath = "${task.directory}/${task.fileName}";
    task.creationTimeStamp = DateTime.now().millisecondsSinceEpoch;
    task.creationTime = VideoUtils.timestampToLocalTime(task.creationTimeStamp);
    task.queue = parentTask.queue;
    task.status = TaskStatus.notFound;

    // LogUtils().logger.d("添加任务：${task.filePath}");

    // 重复任务的话删掉原本的任务添加新的任务,如果已经下载过的话会直接成功
    for (int i = 0; i < _taskQueue.length; i++) {
      if (task.filePath == _taskQueue[i].filePath) {
        _taskQueue.removeAt(i--);
        break;
      }
    }

    _taskQueue.add(task);

    //提前检查文件的大小
    final file = File(task.filePath);
    // 检查文件是否存在并获取已下载大小
    if (file.existsSync()) {
      task.downloadedBytes = file.lengthSync();
      // _downloadedBytes[task.url] = downloadedBytes;
    } else {
      if (!useFfmpeg) {
        //ffmpeg下载时不用预创建文件
        file.createSync();
      }
    }

    // 获取文件总大小（可选，用于验证 Range 支持）
    task.totalLength = await _getContentLength(_dio, task.url);
    if (useFfmpeg) {
      //托管下载，拿到任务后就认为是进行中
      task.status = TaskStatus.running;
    } else {
      task.status = TaskStatus.enqueued;
    }

    // 如果已完成下载，直接返回
    if (task.downloadedBytes == task.totalLength) {
      task.status = TaskStatus.complete;
      if (kDebugMode) {
        print('File already downloaded.');
      }
      return;
    }
    if (!useFfmpeg) {
      //拿到头信息后马上就开始下载
      _tryStartNextTask();
    }
  }

  void startDownloadByFfmpeg() async {
    outputFilePath = "${parentTask.directory}/output.mp4";
    File outputFile = File(outputFilePath);
    if (outputFile.existsSync()) {
      //文件存在就校验本地文件大小，基本可以认为本地文件超过分片文件的总大小就可以表示下载已结束（部分文件的实际大小会比所有分片文件总大小大，不会有更小的情况），不需要下载了
      //本期先不比对文件大小，本地存在就认为已经下载完成

      //所有分片改为已下载完成
      for (var task in _taskQueue) {
        task.status = TaskStatus.complete;
        task.downloadedBytes = task.totalLength;
        task.progress = 1;
      }
      onCallBackProgress();
      onCallBackSuccess();
      return;
    }
    downloadUrlByFfmpegToMp4(
        url: parentTask.variantUrl,
        outputFilePath: outputFilePath,
        onProgress: (double progress, int downloadedBytes, int totalBytes, int downloadedSegments, int totalSegments, int speed) {
          onProgress(progress, downloadedBytes, totalBytes, downloadedSegments, totalSegments, speed);
        },
        onFailed: (String msg) {
          onFailed(msg);
        },
        onSuccess: (String fileMp4Path, double progress, int downloadedBytes, int totalBytes, int downloadedSegments, int totalSegments) {
          onSuccess(
            fileMp4Path,
            1,
            downloadedBytes,
            totalBytes,
            downloadedSegments,
            totalSegments,
          );
        });
  }

  // 尝试启动下一个任务
  void _tryStartNextTask() async {
    if (parentTask.status != TaskStatus.running) {
      //父任务没有在运行，则不执行下载任务
      return;
    }

    if (findRunningTask() >= maxConcurrentDownloads || _taskQueue.isEmpty) {
      return;
    }

    final task = findWaitTask();
    if (task == null) {
      return;
    }
    task.status = TaskStatus.running;

    task.cancelToken = CancelToken();

    try {
      final file = File(task.filePath);

      // 设置 Range 请求头实现断点续传
      final options = Options(
        headers: {"Range": "bytes=${task.downloadedBytes}-"},

        /// Receive response data as a stream
        responseType: ResponseType.stream,
        // followRedirects: false,
      );

      // await _dio.download(
      //   task.url,
      //   task.savePath,
      //   cancelToken: cancelToken,
      //   options: options,
      //   deleteOnError: false,
      //   onReceiveProgress: (received, total) {
      //     if (total != -1 && task.onProgress != null) {
      //       task.onProgress!(received / total);
      //     }
      //   },
      // );

      int lastDownloadedByte = task.downloadedBytes;
      // 发起请求
      final response = await _dio.get(
        task.url,
        cancelToken: task.cancelToken,
        options: options,
        onReceiveProgress: (int received, int total) {
          // LogUtils().logger.d(received);
          if (total != -1) {
            task.progress = double.parse(((received / total) * 100).toStringAsFixed(2));
            task.downloadedBytes = lastDownloadedByte + received;
            // LogUtils().logger.d("已接收到的字节数：${task.downloadedBytes}");
          }
        },
      );

      // 打开文件，以追加模式写入数据
      final raf = file.openSync(mode: FileMode.append);
      final stream = response.data!.stream;

      // 写入数据并更新进度
      // int currentDownloaded = downloadedBytes;
      await for (var chunk in stream) {
        raf.writeFromSync(chunk);
        // currentDownloaded += chunk.length;
        // onProgress(currentDownloaded, totalLength);
      }

      await raf.close();
      task.status = TaskStatus.complete;
      if (isAllTaskComplete()) {
        //这里可以进行判断已下载的bytes和总共的bytes是否一致不一致的话可以认为下载失败了(这个版本暂时不做)
        stopTimer();
        onCallBackProgress();
        onCallBackSuccess();
      }
      // print("Download completed: ${task.url}");
    } catch (e) {
      if (e is DioException) {
        task.status = TaskStatus.paused;
        if (CancelToken.isCancel(e)) {
          pauseAllDownload();
          task.status = TaskStatus.paused;
        } else if (e.response != null && e.response!.statusCode == 416) {
          // 文件继续下载异常，range超限，这种情况下通常是拼接文件导致的，删除本地文件有可能会恢复下载

          // if (task.retryCount < 3) {
          //
          //   File file = File(task.filePath);
          //   file.deleteSync();
          //   task.status = TaskStatus.enqueued;
          //   task.retryCount++;
          //   _tryStartNextTask();
          // } else {
          pauseAllDownload();
          //超过重试次数后直接失败不在重试
          task.status = TaskStatus.failed;
          callbackFailed(e.toString());
          // }
        } else {
          //其他异常
          // if (task.retryCount < 3) {
          //   task.status = TaskStatus.enqueued;
          //   task.retryCount++;
          //   _tryStartNextTask();
          // } else {
          pauseAllDownload();
          //超过重试次数后直接失败不在重试
          task.status = TaskStatus.failed;
          callbackFailed(e.toString());
          // }
        }
      } else {
        // print("Download error: $e");
        //其他异常
        // if (task.retryCount < 3) {
        //   task.status = TaskStatus.enqueued;
        //   task.retryCount++;
        //   _tryStartNextTask();
        // } else {
        pauseAllDownload();
        //超过重试次数后直接失败不在重试
        task.status = TaskStatus.failed;
        callbackFailed(e.toString());
        // }
      }
    } finally {
      task.cancelToken = null;
      _tryStartNextTask();
    }
  }

  ///计算总计需要下载的总字节数(因为任务是异步添加的所以可能会有延时拿到总计下载大小的情况发生)
  void calculateTotalBytes() {
    int t = 0;
    //分片信息请求完后,计算分片基础信息
    for (var task in _taskQueue) {
      t += task.totalLength;
    }
    totalBytes = t;
    totalSegments = _taskQueue.length;
  }

  ///使用ffmpeg下载url文件转为mp4
  Future<void> downloadUrlByFfmpegToMp4({
    required String url,
    required String outputFilePath,
    required void Function(double progress, int downloadedBytes, int totalBytes, int downloadedSegments, int totalSegments, int speed) onProgress,
    required void Function(String msg) onFailed,
    required void Function(String fileMp4Path, double progress, int downloadedBytes, int totalBytes, int downloadedSegments, int totalSegments)
        onSuccess,
  }) async {
    final List<String> command = [
      "-protocol_whitelist",
      "file,crypto,http,https,tcp,tls",
      '-i',
      url,
      '-c',
      'copy',
      '-f',
      'mp4',
      outputFilePath,
    ];

    File outputFile = File(outputFilePath);
    if (outputFile.existsSync()) {
      //已存在则直接删除，为了避免异常退出的情况
      outputFile.deleteSync();
    }
    Directory directory = outputFile.parent;
    if (!directory.existsSync()) {
      directory.createSync(recursive: true);
    }

    //命令执行结束回调
    completeCallback(FFmpegSession session) async {
      // print(session.getOutput());
      ReturnCode? returnCode = await session.getReturnCode();
      if (returnCode == null) {
        onFailed("下载失败");
        return;
      }

      if (ReturnCode.isSuccess(returnCode)) {
        stopTimer();
        if (outputFile.existsSync()) {
          onProgress(1, outputFile.lengthSync(), totalBytes, totalSegments, totalSegments, totalSpeed);
          onSuccess(outputFile.path, 1, outputFile.lengthSync(), outputFile.lengthSync(), totalSegments, totalSegments);
        } else {
          onFailed("下载失败");
        }
      } else {
        if (parentTask.status != TaskStatus.paused) {
          //ffmpeg取消下载也会触发失败，所以这里做个判断不要回调到了失败那里
          onFailed("下载失败");
        }
      }
    }

    //日志回调
    logCallback(Log log) {
      if (parentTask.status == TaskStatus.paused) {
        pauseAllDownload();
        return;
      }
      if (kDebugMode) {
        print("====================  ${log.getMessage()}");
      }
    }

    int downloadedSegments = 1;

    ///分片下载统计接口
    statisticsCallback(Statistics statistics) {
      // print(statistics.toString());
      // LogUtils().logger.d(statistics.toString());
      // LogUtils().logger.d("${file.existsSync()} - ${file.lengthSync()}");
      if (!outputFile.existsSync()) {
        return;
      }
      parentTask.filePath = outputFilePath;
      //ffmpeg返回的速度在某些情况下有问题，所以这里手动计算
      // totalSpeed = (statistics.getSpeed() * 1024 * 1024).toInt();
      // if (totalSpeed < 0) {
      //   totalSpeed = 0;
      // }

      //根据文件的写入速度来计算下载速度
      int currentTime = DateTime.now().millisecondsSinceEpoch;
      int timeDiff = currentTime - lastProgressTime; // 计算时间间隔（毫秒）
      if (timeDiff > 0) {
        int downloadedBytes = File(parentTask.filePath).lengthSync();

        int downloadedDiffBytes = downloadedBytes - lastDownloadedBytes; // 计算这段时间内下载的字节数
        totalSpeed = downloadedDiffBytes ~/ timeDiff * 1000; // 计算速度（字节/秒）

        lastProgressTime = DateTime.now().millisecondsSinceEpoch;
        lastDownloadedBytes = downloadedBytes;
      }

      //计算进度
      double duration = statistics.getTime() / 1000;
      double progress = (duration / parentTask.duration);
      if (progress > 1) {
        //header里的总时长不一定准确，当发生异常的时候手动纠正
        progress = 1;
      }
      if (outputFile.lengthSync() > totalBytes) {
        totalBytes = outputFile.lengthSync();
      }
      onProgress(double.parse(progress.toStringAsFixed(3)), outputFile.lengthSync(), totalBytes, downloadedSegments++, totalSegments, totalSpeed);
    }

    session = await FFmpegKit.executeWithArgumentsAsync(command, completeCallback, logCallback, statisticsCallback);

    // final returnCode = await session.getReturnCode();
    // print(returnCode);
  }

  Future<int> _getContentLength(Dio dio, String url) async {
    try {
      final response = await dio.head(url);
      return int.parse(response.headers.value('content-length')!);
    } catch (e) {
      // throw Exception('Failed to get content length: $e');
      return 0;
    }
  }

  ///停止进度回调
  void stopTimer() {
    if (progressTimer != null) {
      progressTimer!.cancel();
      progressTimer = null;
    }
  }

  ///档期那是否有进行中的任务
  bool isHadRunningTask() {
    for (var task in _taskQueue) {
      if (task.status == TaskStatus.running) {
        return true;
      }
    }
    return false;
  }

  ///判断所有分片是否下载完成
  bool isAllTaskComplete() {
    for (var task in _taskQueue) {
      if (task.status != TaskStatus.complete) {
        return false;
      }
    }
    return true;
  }

  // 暂停下载
  void pauseAllDownload() {
    stopTimer();
    callbackPaused();
    parentTask.status = TaskStatus.paused;
    for (var task in _taskQueue) {
      task.status = TaskStatus.paused;
      if (task.cancelToken != null) {
        task.cancelToken!.cancel("用户暂停");
      }
    }
    if (session != null) {
      session!.cancel();
      //删除整个目录
      Directory directory = Directory(parentTask.directory);
      if (directory.existsSync()) {
        directory.deleteSync(recursive: true);
      }
    }
  }

  bool _isPaused = false;

  ///暂停回调这样做是为了防止多次回调
  void callbackPaused() {
    if (!_isPaused) {
      _isPaused = true;
      onPause();
      // 延迟后重置标志位，允许再次触发
      Future.delayed(const Duration(milliseconds: 300), () {
        _isPaused = false;
      });
    }
  }

  bool _isFailed = false;

  ///暂停回调这样做是为了防止多次回调
  void callbackFailed(String msg) {
    if (!_isFailed) {
      _isFailed = true;
      onFailed(msg);
      // 延迟后重置标志位，允许再次触发
      Future.delayed(const Duration(milliseconds: 500), () {
        _isFailed = false;
      });
    }
  }

  ///下载完成的回调
  void onCallBackSuccess() async {
    stopTimer();
    if (!isUseFfmpeg()) {
      outputFilePath = "${parentTask.directory}/output.mp4";
      M3u8Utils.generateLocalM3u8File(
        m3u8FileDir: parentTask.directory,
        durationSeconds: parentTask.duration,
      );

      await M3u8Utils.mergeMp4Files(dir: parentTask.directory, outputFilePath: outputFilePath, deleteSegmentsFiles: true);
      //合并完成后删除本地已经下载的切片文件
    }

    onSuccess(outputFilePath, 1, totalBytes, totalBytes, _taskQueue.length, _taskQueue.length);
  }

  ///回调进度（非ffmpeg时使用）
  void onCallBackProgress() {
    //已下载部分的bytes
    int downloadedBytes = 0;
    int totalBytes = 0;
    int downloadedSegments = 0;
    int totalSegments = _taskQueue.length;
    for (var task in _taskQueue) {
      downloadedBytes += task.downloadedBytes;
      totalBytes += task.totalLength;
      if (task.status == TaskStatus.complete) {
        downloadedSegments++;
      }
    }

    double bytesRatio = 0;
    if (totalBytes > 0) {
      bytesRatio = (downloadedBytes / totalBytes);
    }

    String p = bytesRatio.toStringAsFixed(3);

    onProgress(double.parse(p), downloadedBytes, totalBytes, downloadedSegments, totalSegments, totalSpeed);
  }

  ///使用ffmpeg下载时回调进度
  // void onCallBackProgressByFfmpeg({required String outputPath }) {
  //   File file=File(outputPath);
  //
  //   //已下载部分的bytes
  //   int downloadedBytes = file.lengthSync();
  //   int totalBytes = file.lengthSync();
  //   int downloadedSegments = 0;
  //   int totalSegments = _taskQueue.length;
  //
  //   double bytesRatio = 0;
  //   if (totalBytes > 0) {
  //     bytesRatio = (downloadedBytes / totalBytes);
  //   }
  //
  //   String p = (bytesRatio * 100).toStringAsFixed(2);
  //
  //   onProgress(p, downloadedBytes, totalBytes, downloadedSegments, totalSegments);
  // }

  ///查询当前等待的任务
  M3u8DownloadSegmentTask? findWaitTask() {
    for (var task in _taskQueue) {
      if (task.status == TaskStatus.enqueued) {
        return task;
      }
    }
    return null;
  }

  ///查询正在下载中的任务数量
  int findRunningTask() {
    int count = 0;
    for (var task in _taskQueue) {
      if (task.status == TaskStatus.running) {
        count++;
      }
    }
    return count;
  }

  ///判断是否使用ffmpeg来进行下载（规则大致为音视频分离的时候，非ts文件格式的时候）
  bool isUseFfmpeg() {
    if (parentTask.mediaPlaylistUrls.isNotEmpty) {
      return true;
    }
    // 判断是否是ts文件
    if (parentTask.segments!.any((e) => !e.url!.contains(".ts"))) {
      return true;
    }

    return false;
  }
}

///分片任务
class M3u8DownloadSegmentTask {
  final String url;
  String fileName;
  String filePath;
  String directory;
  String creationTime;
  int creationTimeStamp;

  ///同下载任务的分组
  String group;

  ///同一个m3u8的分片文件 queue是一样的
  String queue;
  TaskStatus status;

  ///百分比进度 0-1
  double progress;

  ///下载速度字节
  int speedBytes;

  /// 已下载字节数
  int downloadedBytes;

  ///总字节数
  int totalLength;

  ////文件大小
  int expectedFileSize;

  ///运行时的变量
  CancelToken? cancelToken;

  ///自己记录的重试次数，因为重试有自定义的逻辑所以这样做
  // int retryCount;

  M3u8DownloadSegmentTask({
    required this.url,
    this.cancelToken,
    this.fileName = "",
    this.filePath = "",
    this.directory = "",
    this.creationTime = "",
    this.creationTimeStamp = 0,
    this.group = "default",
    this.queue = "",
    this.status = TaskStatus.enqueued,
    this.progress = 0,
    this.expectedFileSize = 0,
    this.downloadedBytes = 0,
    this.totalLength = 0,
    // this.retryCount = 0,
    this.speedBytes = 0,
  });
}
